package server

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"html/template"
	"net/http"
	"server/components/auth"
	"server/components/logger"
	"server/components/models/ent/user"
	"server/components/repository"
	"time"

	_ "embed"
	_ "server/components/server/docs"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"
	httpswagger "github.com/swaggo/http-swagger"
	"go.uber.org/zap/zapcore"
)

type Option func(s *server)

var (
	//go:embed www/css/pure-min.css
	pureCss []byte

	//go:embed www/css/extension.css
	extCss []byte

	//go:embed www/index.html
	indexPage []byte
)

type server struct {
	port   int
	pg     string
	clean  bool
	server *http.Server
}

func WithCleanDB(v bool) Option {
	return func(s *server) {
		s.clean = v
	}
}

func WithPort(p int) Option {
	return func(s *server) {
		s.port = p
	}
}

func WithPGUrl(pg string) Option {
	return func(s *server) {
		s.pg = pg
	}
}

func NewServer(options ...Option) *server {
	server := &server{}
	for _, opt := range options {
		opt(server)
	}
	return server
}

func (s *server) Run(ctx context.Context) error {
	logger.Logger().Debugf("Running server on port %v with DB at %v", s.port, s.pg)
	if err := repository.InitConnection(ctx, s.pg, s.clean); err != nil {
		return fmt.Errorf("cannot init DB connection %w", err)
	}
	// Обработка SIGTERM (ctrl-c)
	go func(ctx context.Context) {
		<-ctx.Done()
		logger.Logger().Infof("Shutting down the server")
		s.server.Shutdown(context.Background())
		repository.CloseConnection(context.Background())
	}(ctx)

	if err := s.serve(ctx); err != nil {
		return fmt.Errorf("server failed %w", err)
	}
	return nil
}

// @title           Swagger API
// @version         1.0
// @description     The Weather server API
// @termsOfService  http://swagger.io/terms/

// @contact.name   API Support
// @contact.url    http://www.swagger.io/support
// @contact.email  support@swagger.io

// @license.name  Apache 2.0
// @license.url   http://www.apache.org/licenses/LICENSE-2.0.html

// @host      localhost:8088
// @BasePath  /

// @externalDocs.description  OpenAPI
// @externalDocs.url          https://swagger.io/resources/open-api/
func (s *server) serve(_ context.Context) error {
	mux := chi.NewRouter()
	mux.Use(middleware.Recoverer)
	mux.Use(middleware.CleanPath)
	if logger.Logger().Level() == zapcore.DebugLevel {
		mux.Use(middleware.RequestLogger(&middleware.DefaultLogFormatter{Logger: logger.RestLogger(), NoColor: true}))
	}
	cors := cors.New(cors.Options{
		AllowedOrigins:   []string{"*"},
		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
		AllowCredentials: true,
		MaxAge:           300, // Maximum value not ignored by any of major browsers
	})
	mux.Use(cors.Handler)

	// Healthcheck для проверки что сервер доступен
	mux.Get("/heartbeat", heartbeat)
	mux.Method("GET", "/swagger/*", httpswagger.WrapHandler)

	// Авторизация и заведение юзеров
	mux.Post("/registration", auth.RegisterAccount)
	mux.Post("/login", auth.Login)

	// Управление юзерами
	mux.Group(func(r chi.Router) {
		r.Use(auth.VerifyHandler)
		r.Get("/accounts/{accountId}", auth.GetAccount)
		r.Get("/accounts/search", searchAccounts)
		r.Put("/accounts/{accountId}", auth.UpdateAccount)
		r.Delete("/accounts/{accountId}", auth.DeleteAccount)
	})

	// Управление регионами
	mux.Group(func(r chi.Router) {
		r.Use(auth.VerifyHandler)
		r.Get("/region/{regionId}", getRegionById)
		r.Post("/region", addRegion)
		r.Put("/region/{regionId}", updateRegion)
		r.Delete("/region/{regionId}", deleteRegion)
		r.Get("/region/types/{typeId}", getRegionTypeById)
		r.Post("/region/types", addRegionType)
		r.Put("/region/types/{typeId}", updateRegionType)
		r.Delete("/region/types/{typeId}", deleteRegionType)
	})

	// Управление погодой
	mux.Group(func(r chi.Router) {
		r.Use(auth.VerifyHandler)
		r.Get("/region/weather/{regionId}", getWeatherByRegionId)
		r.Get("/region/weather/search", searchWeather)
		r.Post("/region/weather", addWeather)
		r.Put("/retion/weather/{regionId}", updateRegionWeather)
		r.Delete("/region/weather/{regionId}", deleteRegionWeather)
		r.Post("/region/{regionId}/weather/{weatherId}", setRegionWeather)
		r.Delete("/region/{regionId}/weather/{weatherId}", unsetRegionWeather)
		r.Get("/region/weather/forecast/{forecastId}", getForecast)
		r.Put("/region/weather/forecast/{forecastId}", updateForecast)
		r.Post("/region/weather/forecast", addForecast)
		r.Delete("/region/weather/forecast/{forecastId}", deleteForecast)
	})

	// Вебка
	mux.Group(func(r chi.Router) {
		r.Get("/css/{style:*.css}", func(w http.ResponseWriter, r *http.Request) {
			f := chi.URLParam(r, "style")
			w.Header().Add("Content-Type", "text/css")
			switch f {
			case "pure-min.css":
				http.ServeContent(w, r, f, time.Now().UTC(), bytes.NewReader(pureCss))
			case "extension.css":
				http.ServeContent(w, r, f, time.Now().UTC(), bytes.NewReader(extCss))
			}
		})
		index := func(w http.ResponseWriter, r *http.Request) {
			tmpl, err := template.New("index.html").
				Funcs(template.FuncMap{
					"isOdd": func(arg int) bool { return arg%2 != 0 },
				}).Parse(string(indexPage))
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			vec, err := repository.EntClient().User.Query().Order(user.ByID()).All(r.Context())
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			tmpl.Execute(w, map[string]interface{}{
				"Accounts": vec,
			})
		}
		r.Get("/index.html", index)
		r.Get("/", index)
		r.Post("/www/register", func(w http.ResponseWriter, r *http.Request) {
			if err := r.ParseForm(); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
			fn := r.FormValue("first-name")
			ln := r.FormValue("last-name")
			pwd := r.FormValue("pwd")
			email := r.FormValue("email")
			if err := auth.RegisterAccountRaw(r.Context(), auth.RegisterAccountRequest{
				FirstName: fn,
				LastName:  ln,
				Email:     email,
				Password:  pwd,
			}); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			} else {
				http.Redirect(w, r, "/", http.StatusFound)
			}
		})
	})

	s.server = &http.Server{
		Handler:      mux,
		Addr:         fmt.Sprintf(":%v", s.port),
		WriteTimeout: time.Duration(15) * time.Minute,
		ReadTimeout:  time.Duration(15) * time.Minute,
	}
	if err := s.server.ListenAndServe(); err != nil {
		if !errors.Is(err, http.ErrServerClosed) {
			logger.Logger().Errorf("Failed to start REST server %v", err)
			return err
		}
	}
	return nil
}
